<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
<meta name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">

    <meta name="author" content="Ian Tang">


    <meta name="subtitle" content="岁月不居时节如流">


    <meta name="description" content="好记性不如烂笔头">



<title>随笔</title>



    <link rel="icon" href="/image/The_Lion_King_32_32.png">




    <!-- stylesheets list from _config.yml -->
    
    <link rel="stylesheet" href="/css/style.css">
    



    <!-- scripts list from _config.yml -->
    
    <script src="/js/script.js"></script>
    
    <script src="/js/tocbot.min.js"></script>
    



    
    
        
    


<meta name="generator" content="Hexo 6.3.0"></head>

<body>
    <script>
        // this function is used to check current theme before page loaded.
        (() => {
            const currentTheme = window.localStorage && window.localStorage.getItem('theme') || '';
            const isDark = currentTheme === 'dark';
            const pagebody = document.getElementsByTagName('body')[0]
            if (isDark) {
                pagebody.classList.add('dark-theme');
                // mobile
                document.getElementById("mobile-toggle-theme").innerText = "· Dark"
            } else {
                pagebody.classList.remove('dark-theme');
                // mobile
                document.getElementById("mobile-toggle-theme").innerText = "· Light"
            }
        })();
    </script>

    <div class="wrapper">
        <header>
    <nav class="navbar">
        <div class="container">
            <div class="navbar-header header-logo"><a href="/">Tangkaifei&#39;s Blog</a></div>
            <div class="menu navbar-right">
                
                    <a class="menu-item" href="/archives">Posts</a>
                
                    <a class="menu-item" href="/category">Categories</a>
                
                    <a class="menu-item" href="/tag">Tags</a>
                
                    <a class="menu-item" href="/about">About</a>
                
                <input id="switch_default" type="checkbox" class="switch_default">
                <label for="switch_default" class="toggleBtn"></label>
            </div>
        </div>
    </nav>

    
    <nav class="navbar-mobile" id="nav-mobile">
        <div class="container">
            <div class="navbar-header">
                <div>
                    <a href="/">Tangkaifei&#39;s Blog</a><a id="mobile-toggle-theme">·&nbsp;Light</a>
                </div>
                <div class="menu-toggle" onclick="mobileBtn()">&#9776; Menu</div>
            </div>
            <div class="menu" id="mobile-menu">
                
                    <a class="menu-item" href="/archives">Posts</a>
                
                    <a class="menu-item" href="/category">Categories</a>
                
                    <a class="menu-item" href="/tag">Tags</a>
                
                    <a class="menu-item" href="/about">About</a>
                
            </div>
        </div>
    </nav>

</header>
<script>
    var mobileBtn = function f() {
        var toggleMenu = document.getElementsByClassName("menu-toggle")[0];
        var mobileMenu = document.getElementById("mobile-menu");
        if(toggleMenu.classList.contains("active")){
           toggleMenu.classList.remove("active")
            mobileMenu.classList.remove("active")
        }else{
            toggleMenu.classList.add("active")
            mobileMenu.classList.add("active")
        }
    }
</script>
            <div class="main">
                <div class="container">
    
    
        <div class="post-toc">
    <div class="tocbot-list">
    </div>
    <div class="tocbot-list-menu">
        <a class="tocbot-toc-expand" onclick="expand_toc()">Expand all</a>
        <a onclick="go_top()">Back to top</a>
        <a onclick="go_bottom()">Go to bottom</a>
    </div>
</div>

<script>
    var tocbot_timer;
    var DEPTH_MAX = 6; // 为 6 时展开所有
    var tocbot_default_config = {
        tocSelector: '.tocbot-list',
        contentSelector: '.post-content',
        headingSelector: 'h1, h2, h3, h4, h5',
        orderedList: false,
        scrollSmooth: true,
        onClick: extend_click,
    };

    function extend_click() {
        clearTimeout(tocbot_timer);
        tocbot_timer = setTimeout(function() {
            tocbot.refresh(obj_merge(tocbot_default_config, {
                hasInnerContainers: true
            }));
        }, 420); // 这个值是由 tocbot 源码里定义的 scrollSmoothDuration 得来的
    }

    document.ready(function() {
        tocbot.init(obj_merge(tocbot_default_config, {
            collapseDepth: 1
        }));
    });

    function expand_toc() {
        var b = document.querySelector('.tocbot-toc-expand');
        var expanded = b.getAttribute('data-expanded');
        expanded ? b.removeAttribute('data-expanded') : b.setAttribute('data-expanded', true);
        tocbot.refresh(obj_merge(tocbot_default_config, {
            collapseDepth: expanded ? 1 : DEPTH_MAX
        }));
        b.innerText = expanded ? 'Expand all' : 'Collapse all';
    }

    function go_top() {
        window.scrollTo(0, 0);
    }

    function go_bottom() {
        window.scrollTo(0, document.body.scrollHeight);
    }

    function obj_merge(target, source) {
        for (var item in source) {
            if (source.hasOwnProperty(item)) {
                target[item] = source[item];
            }
        }
        return target;
    }
</script>
    

    
    <article class="post-wrap">
        <header class="post-header">
            <h1 class="post-title"></h1>
            
                <div class="post-meta">
                    
                        Author: <a itemprop="author" rel="author" href="/">Ian Tang</a>
                    

                    
                        <span class="post-time">
                        Date: <a href="#">十一月 27, 2022&nbsp;&nbsp;13:29:20</a>
                        </span>
                    
                    
                </div>
            
        </header>

        <div class="post-content">
            <hr>
<p>title: 网络平台开发套件DPDK<br>date: 2022-11-27 11:54<br>tags:</p>
<ul>
<li>network</li>
<li>dpdk</li>
</ul>
<p>categories:</p>
<ul>
<li>技术：知识技能</li>
</ul>
<p>comments: true<br>toc: true</p>
<hr>
<p>最近在做T4P4s多目标编译器，崔博要求T4P4s的前端编译要与DPDK结合起来实现数据平面的完全可编程，并且数据带宽达到40Gbs。就看一下DPDK这本书，结合白皮书入入门。</p>
<p>在早期的网络收发数据量不是特别大的时候，比如10Mbs、100Mbs的传输速率，数据报文的处理都是经过通用架构的Linux内核态，一个报文到来产生一次中断请求，cpu去处理网络报文，当传输速率提升到万兆，甚至英特尔推出的40Gbs、100Gbs的传输速率标准时，仅靠cpu5Ghz的时钟频率显然是不够看的，因此将网络数据的处理从内核态转到应用态是一个发展方向。再此之前的解决方案有轮询，指的是一次中断批处理一批网络报文，而不是一个报文中断一次；还有内存共享技术，减少处理的数据从内核复制到用户的消耗。</p>
<h2 id="数据包处理能力"><a href="#数据包处理能力" class="headerlink" title="数据包处理能力"></a>数据包处理能力</h2><p>吞吐、延迟、丢包率、抖动</p>
<p>对于转发，常以包转发率（每秒包转发率pps）而不是bit&#x2F;s来衡量转发效率</p>
<p>一般所说的接口带宽，1Gbit&#x2F;s、10Gbit&#x2F;s、25Gbit&#x2F;s、40Gbit&#x2F;s、100Gbit&#x2F;s，代表以太接口线路上所能承载的最高传输比特率，其单位是 bit&#x2F;s(bit per second， 位 &#x2F; 秒)。实际上，不可能每个比特都传输有效数据。以太网每个帧之间会有帧间距(Inter- Packet Gap，IPG)，默认帧间距大小为 12 字节。每个帧还有 7 个字节的前导(Preamble)，和 1 个字节的帧首定界符(Start Frame Delimiter，SFD)。</p>
<h2 id="Cache和内存"><a href="#Cache和内存" class="headerlink" title="Cache和内存"></a>Cache和内存</h2><h3 id="系统架构的演进"><a href="#系统架构的演进" class="headerlink" title="系统架构的演进"></a>系统架构的演进</h3><p>经典计算机系统中的内存架构：</p>
<p><a target="_blank" rel="noopener" href="https://pic.downk.cc/item/5f19765614195aa594489c4e.jpg"><img src="https://pic.downk.cc/item/5f19765614195aa594489c4e.jpg"></a></p>
<p>所有的数据交换都需要经过北桥</p>
<ol>
<li>处理器访问内存</li>
<li>处理器访问外设</li>
<li>外设访问处理器</li>
<li>外设访问内存</li>
</ol>
<p>因此，北桥的性能成了系统性能的瓶颈，因此推出了一种架构：</p>
<p><img src="https://pic.downk.cc/item/5f19783614195aa59449ad36.png"></p>
<p>这里增加了内存的访问带宽，缓解了不同设备对同一内存访问的拥塞问题，但是没有改进单一北桥芯片的瓶颈问题。为了解决这个问题，产生了NUMA(non-uniform memory architecture非一致性内存架构)系统</p>
<p><img src="https://pic.downk.cc/item/5f1ae97214195aa594168cb0.png"></p>
<p>这样的系统架构不需要一个复杂的北桥就能将内存带宽增加到以前的四倍，但是该系统也有缺点，访问内存所花费的时间与处理器有关，如果处理器访问本地内存时间很短，如果访问远程内存，则需要跨越一个或者两个cpu。因此访问内存的时间也是不确定的。</p>
<h3 id="Cache系统简介"><a href="#Cache系统简介" class="headerlink" title="Cache系统简介"></a>Cache系统简介</h3><p>Cache有三级，以前一级和二级放在处理器内部，三级放在主板上，主要是因为三级cache太大，集成进处理器需要的晶体管数量太多，现在工艺都到个位数纳米了，可以集成更多的晶体管，三级cache也被集成进处理器了。</p>
<ul>
<li><p>一级cache</p>
<p>一般分为数据cache和指令cache，数据cache主要用来存储数据，而指令cache用于存放指令。这种cache处理速度最快，一般只需要3-5个指令周期就可以访问到数据，因此成本高，容量小，一般都只有几十kb。在多核处理器内部，每个核心都拥有属于自己的一级cache。</p>
</li>
<li><p>二级cache</p>
<p>数据和指令都无差别的存放在一起。速度比一级cache要慢，处理器大约需要十几个处理周期才能访问到数据，一般几百kb到几mb，在多核处理器内部每个核都拥有自己的二级cache。</p>
</li>
<li><p>三级cache</p>
<p>多核处理器共用一个cache。导致一个问题，有可能一个内核占用了大部分的三级cache，而其他内核用极小一部分cache，从而导致cache不命中，性能下降</p>
</li>
</ul>
<h4 id="TLB-cache"><a href="#TLB-cache" class="headerlink" title="TLB cache"></a>TLB cache</h4><p>MMU的一部分</p>
<p> 当程序员直接访问物理地址进行编程时，当程序出现错误，整个系统都崩掉了；或者一个应用程序调用另一个应用程序的写操作，会让另一个应用程序崩掉。因此提出了虚拟内存和分段分页技术。</p>
<p>软件使用虚拟地址访问内存，而处理器负责虚拟地址到物理地址的映射工作，为了完成映射，处理器采取多级页表来进行多次查找找到真正的物理地址。如果查找不到就会产生缺页异常，不会影响到其他的应用程序。</p>
<p>页表存储在内存中，当需要根据虚拟地址查找物理地址时，就要到内存中寻找页目录表和页存表。如果采用TLB Cache的话，集成到处理器内部，就可以大大缩减访问内存的时间，TLB Cache缓存内存中的页表项，采用相连存储器，直接搜索虚拟地址，返回物理地址。如果TLB不命中，则要去内存中查找。</p>
<h3 id="Cache地址映射和变换"><a href="#Cache地址映射和变换" class="headerlink" title="Cache地址映射和变换"></a>Cache地址映射和变换</h3><p>Cache一般只有20-30MB，内存都是以GB为单位的，怎么把内存放到缓存里面呢。这就需要一个映射算法和分块机制。</p>
<ul>
<li>分块机制：是指Cache和内存都是以块进行数据交换，块的大小通常都是以内存一个存储周期中能够访问的数据长度为限。主流块的大小时64字节Bytes，因此一个Cache line就是64字节大小的字节块。</li>
<li>映射算法：是指把内存地址空间和Cache地址空间映射起来，把内存中的内容按照某种规则放到Cache中，当处理器需要某个数据块的内容，则可以根据映射规则去Cache中获取</li>
</ul>
<h4 id="全关联性Cache"><a href="#全关联性Cache" class="headerlink" title="全关联性Cache"></a>全关联性Cache</h4><p>全关联型 Cache 是指主存中的任何一块内存都可以映射到 Cache 中的任意一块位置上。在 Cache 中，需要建立一个目录表，目录表的每个表项都有三部分组成:内存块地址、Cache 块号和一个有效位。当处理器需要访问某个内存地址时，首先通过该目录表查询是否该内容缓存在 Cache 中。</p>
<p>首先，用内存的块地址 A 在 Cache 的目录表中进行查询，如果找到等值的内存块地址， 检查有效位是否有效，只有有效的情况下，才能通过 Cache 块号在 Cache 中找到缓存的内 存，并且加上块内地址 B，找到相应数据，这时则称为 Cache 命中，处理器拿到数据返回; 否则称为不命中，处理器则需要在内存中读取相应的数据。</p>
<p>可以看出，使用全关联型 Cache，块的冲突最小(没有冲突)，Cache 的利用率也高，但是需要一个访问速度很快的相联存储器。随着 Cache 容量的增加，其电路设计变得十分复杂，因此只有容量很小的 Cache 才会设计成全关联型的(如一些英特尔处理器中的 TLB Cache)。</p>
<h4 id="直接关联型Cache"><a href="#直接关联型Cache" class="headerlink" title="直接关联型Cache"></a>直接关联型Cache</h4><p>直接关联型 Cache 是指主存中的一块内存只能映射到 Cache 的一个特定的块中。假设 一个 Cache 中总共存在 N 个 Cache line，那么内存被分成 N 等分，其中每一等分对应一个 Cache line。举个简单的例子，假设 Cache 的大小是 2K，而一个 Cache line 的大小是 64B， 那么就一共有 2K&#x2F;64B&#x3D;32 个 Cache line，那么对应我们的内存，第 1 块(地址 0 ~ 63)，第 33 块(地址 64<em>32 ~ 64</em>33-1)，以及第(N<em>32+1)块(地址 64</em>(N-1)~ 64<em>N-1)都被映射到 Cache 第一块中;同理，第 2 块，第 34 块，以及第(N</em>32+2)块都被映射到 Cache 第二块中;可以依次类推其他内存块。</p>
<p>直接关联型 Cache 的目录表只有两部分组成:区号和有效位。首先，内存地址被分成三部分:区号 A、块号 B 和块内地址 C。根据区号 A 在目录表中找到完全相等的区号，并且在有效位有效的情况下，说明该数据在 Cache 中，然后通过内存地址的块号 B 获得在 Cache 中的块地址，加上块内地址 C，最终找到数据。如果在目录表中 找不到相等的区号，或者有效位无效的情况下，则说明该内容不在 Cache 中，需要到内存中 读取。</p>
<p>可以看出，直接关联是一种很“死”的映射方法，当映射到同一个 Cache 块的多个内存块同时需要缓存在 Cache 中时，只有一个内存块能够缓存，其他块需要被“淘汰”掉。因此，直接关联型命中率是最低的，但是其实现方式最为简单，匹配速度也最快。</p>
<h4 id="组关联型Cache"><a href="#组关联型Cache" class="headerlink" title="组关联型Cache"></a>组关联型Cache</h4><p>组关联型 Cache 是目前 Cache 中用的比较广泛的一种方式，是前两种 Cache 的折中形 式。在这种方式下，内存被分为很多组，一个组的大小为多个 Cache line 的大小，一个组映 射到对应的多个连续的 Cache line，也就是一个 Cache 组，并且该组内的任意一块可以映射 到对应 Cache 组的任意一个。可以看出，在组外，其采用直接关联型 Cache 的映射方式，而 在组内，则采用全关联型 Cache 的映射方式。</p>
<p>假设有一个 4 路组关联型 Cache，其大小为 1M，一个 Cache line 的大小为 64B，那么 总共有 16K 个 Cache line，但是在 4 路组关联的情况下，我们并不是简简单单拥有 16K 个 Cache line，而是拥有了 4K 个组，每个组有 4 个 Cache line。一个内存单元可以缓存到它所 对应的组中的任意一个 Cache line 中去。</p>
<p>以 4 路组关联型 Cache 为例介绍其在 Cache 中的查找过程。目录表由三部分组成，分别是“区号 + 块号”、Cache 块号和有效位。当收到一个内存地址时，该地址被分成四部分:区号 A、组号 B、块号 C 和块内地址 D。首先，根据组号 B 按地址查找到一组目录表项，在 4 路组关联中，则有四个表项，每个表项都有可能存放该内存块;然后，根据区号 A 和块号 C 在该组表项中进行关联查找(即并行查找，为了提高效率)，如果匹配且有效位有 效，则表明该数据块缓存在 Cache 中，得到 Cache 块号，加上块内地址 D，可以得到该内存地址在 Cache 中映射的地址，得到数据;如果没有找到匹配项或者有效位无效，则表示该内存块不在 Cache 中，需要处理器到内存中读取。</p>
<p>实际上，直接关联型 Cache 和全关联型 Cache 只是组关联型 Cache 的特殊情况，当组内 Cache Line 数目为 1 时，即为直接关联型 Cache。而当组内 Cache Line 数目和 Cache 大小相 等时，即整个 Cache 只有一个组，这成为全关联型 Cache。</p>
<h3 id="Cache写策略"><a href="#Cache写策略" class="headerlink" title="Cache写策略"></a>Cache写策略</h3><p>内存的数据被加载到 Cache 后，在某个时刻其要被写回内存，对于这个时刻的选取，有如下几个不同的策略。</p>
<p>直写(write-through):所谓直写，就是指在处理器对 Cache 写入的同时，将数据写入到 内存中。这种策略保证了在任何时刻，内存的数据和 Cache 中的数据都是同步的，这种方式 简单、可靠。但由于处理器每次对 Cache 更新时都要对内存进行写操作，因此总线工作繁 忙，内存的带宽被大大占用，因此运行速度会受到影响。假设一段程序在频繁地修改一个局 部变量，尽管这个局部变量的生命周期很短，而且其他进程 &#x2F; 线程也用不到它，CPU 依然会 频繁地在 Cache 和内存之间交换数据，造成不必要的带宽损失。</p>
<p>回写(write-back):回写相对于直写而言是一种高效的方法。直写不仅浪费时间，而且 有时是不必要的，比如上文提到的局部变量的例子。回写系统通过将 Cache line 的标志位 字段添加一个 Dirty 标志位，当处理器在改写了某个 Cache line 后，并不是马上把其写回内 存，而是将该 Cache line 的 Dirty 标志设置为 1。当处理器再次修改该 Cache line 并且写回到 Cache 中，查表发现该 Dirty 位已经为 1，则先将 Cache line 内容写回到内存中相应的位置， 再将新数据写到 Cache 中。其实，回写策略在多核系统中会引起 Cache 一致性的问题。设想 有两个处理器核心都需要对某个内存块进行读写，其中一个核心已经修改了该数据块，并且 写回到 Cache 中，设置了 Dirty 位;这时另外一个核心也完成了该内存块的修改，并且准备写入到 Cache 中，这时才发现该 Cache line 是“脏”的，在这种情况下，Cache 如何处理呢? 之后的章节我们会继续这个话题。</p>
<p>除了上述这两种写策略，还有 WC(write-combining)和 UC(uncacheable)。这两种策略 都是针对特殊的地址空间来使用的。</p>
<p>write-combining 策略是针对于具体设备内存(如显卡的 RAM)的一种优化处理策略。对 于这些设备来说，数据从 Cache 到内存转移的开销比直接访问相应的内存的开销还要高得 多，所以应该尽量避免过多的数据转移。试想，如果一个 Cache line 里的字被改写了，处理 器将其写回内存，紧接着又一个字被改写了，处理器又将该 Cache line 写回内存，这样就显 得低效，符合这种情况的一个例子就是显示屏上水平相连的像素点数据。write-combining 策 略的引入就是为了解决这种问题，顾名思义，这种策略就是当一个 Cache line 里的数据一个 字一个字地都被改写完了之后，才将该 Cache line 写回到内存中。</p>
<p>uncacheable 内存是一部分特殊的内存，比如 PCI 设备的 I&#x2F;O 空间通过 MMIO 方式被映射成内存来访问。这种内存是不能缓存在 Cache 中的，因为设备驱动在修改这种内存时，总是期望这种改变能够尽快通过总线写回到设备内部，从而驱动设备做出相应的动作。如果放在 Cache 中，硬件就无法收到指令。</p>
<h3 id="Cache-预取"><a href="#Cache-预取" class="headerlink" title="Cache 预取"></a><strong>Cache</strong> 预取</h3><p>就是将内存中的数据提前读取到cache中，这样cpu在下个时钟周期就可以直接从cache中读取数据，不用从内存中读取。前面提到的从一级cache读取数据需要几个时钟周期，从二级需要十几个，从三级cache需要几十个，而从内存读取数据需要几百个时钟周期。</p>
<h3 id="Cache一致性"><a href="#Cache一致性" class="headerlink" title="Cache一致性"></a>Cache一致性</h3><p>我们知道三级cache一般是多个cpu共享的。cache里基本单位是cache line，从32字节到64字节、128字节都有。cpu向内存写的流程：cache 从cpu寄存器里取出，通过内部总线发送到内存；读的流程：从内存发给cache，写入到cpu里的寄存器。</p>
<p>那么内存中的数据缓冲区或者数据结构的起始地址与cache line对齐么？</p>
<p>如果没对齐，则一个小于cache line的数据，有可能会占用两个cache line；如果两个核同时对同一个内存区域进行读写，该怎么操作？（多个核对同一个cache line进行操作）</p>
<p>所以定义数据结构或者数据缓冲区时就申明对齐。</p>
<p>DPDK为了减少Cache一致性的问题，通过对每一个核定义单独的数据结构来避免cpu核共享一个数据结构从而写入同一个cache行。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">lcore_conf</span>&#123;</span></span><br><span class="line">    ...</span><br><span class="line">&#125; _rte_cache_aligned;<span class="comment">//cache line对齐</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">lcore_conf</span> <span class="title">lcore</span>[<span class="title">RTE_MAX_LCORE</span>] _<span class="title">rte_cache_aligned</span>;</span><span class="comment">//定义RTE_MAX_LCORE个数据结构</span></span><br></pre></td></tr></table></figure>

<p>以上定义要求cache line对齐，不会出现一个数据结构横跨两个cache行的问题；对于每个核定义单独的数据结构，防止共享一个结构体，解决了一致性问题。</p>
<p>网卡的多队列问题：</p>
<p>一般网卡都有多队列，接收队列和发送队列都有多个，dpdk中的网卡为每个核都绑定单独的接收队列核发送队列，避免了竞争核cache一致性问题。</p>
<h3 id="DMA"><a href="#DMA" class="headerlink" title="DMA"></a>DMA</h3><p>现代磁盘向内存中拷贝数据时无需借助cpu的帮助，由外接的I&#x2F;O硬件或者主板进行操作，叫做DMA（Direct Memory Access）</p>
<h3 id="零拷贝"><a href="#零拷贝" class="headerlink" title="零拷贝"></a>零拷贝</h3><p>直接把磁盘数据copy到了进程空间中，但实际上一般情况下I&#x2F;O数据是要首先copy到操作系统内部，然后操作系统再copy到进程空间中。<strong>Zero-copy</strong>指无需从内核态向用户态进行拷贝报文，CPU直接在内核态对报文进行操作。</p>
<h2 id="mbuf结构体"><a href="#mbuf结构体" class="headerlink" title="mbuf结构体"></a>mbuf结构体</h2><p><img src="https://img.imgdb.cn/item/600e71543ffa7d37b3e97eaf.jpg"></p>
<p><img src="https://img.imgdb.cn/item/600e71713ffa7d37b3e994ea.jpg"></p>
<p>有一个知乎的帖子<a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/80648748">[译] 写一个简单的内存分配器（替换glibc中的malloc函数）</a>讲解为什么设置这种结构体，很有借鉴意义。</p>
<p><img src="https://img.imgdb.cn/item/603c5ec35f4313ce25289bd7.png"></p>
<h2 id="dpdk抓包工具-pdump"><a href="#dpdk抓包工具-pdump" class="headerlink" title="dpdk抓包工具-pdump"></a>dpdk抓包工具-pdump</h2><p>1、先设置环境变量RTE_SDK&#x3D;&#x2F;home&#x2F;ndsc&#x2F;t4p4s&#x2F;dpdk-19.11&#x2F;</p>
<p>2、安装依赖libpcap的PMD驱动</p>
<p>​		yum install -y libpcap.x86_64 libpcap-devel.x86_64</p>
<p>3、修改 Target Env 的配置文件</p>
<p>​		<code>$ vim dpdk-18.08/x86_64-native-linuxapp-gcc/.config CONFIG_RTE_LIBRTE_PMD_PCAP=y  CONFIG_RTE_LIBRTE_PDUMP=y  </code></p>
<p>4、安装pdump</p>
<p>​		<code>cd dpdk-19.11/app/pdump</code></p>
<p>​		<code>make &amp;&amp; make install</code></p>
<p>5、运行参数</p>
<p>​		<code> ./build/app/dpdk-pdump -- --pdump &#39;port=0,queue=*,rx-dev=/tmp/capture.pcap&#39;</code></p>
<p><img src="https://pic.imgdb.cn/item/60b0474208f74bc1594367fe.jpg"></p>
<h2 id="DPDK应用示例介绍"><a href="#DPDK应用示例介绍" class="headerlink" title="DPDK应用示例介绍"></a>DPDK应用示例介绍</h2><p>这些示例从简单到复杂，但大多数都旨在演示 DPDK 的一个特定功能。下面将重点介绍一些更有趣的示例。</p>
<ul>
<li>Hello World(<strong>Hello World</strong>)：与大多数编程框架的介绍一样，一个好的开始是Hello World应用程序。Hello World 示例设置 DPDK 环境抽象层 （EAL），并打印一个简单的”Hello World”消息到每个启用 DPDK 的核心。此应用程序不执行任何数据包转发，但它是测试 DPDK 环境是否编译和正确设置的一个很好的方法。</li>
<li>基本转发框架（<strong>Basic Forwarding&#x2F;Skeleton App</strong>）：基本转发&#x2F;骨架包含使用DPDK 启用基本数据包转发所需的最小代码量。这允许您测试网络接口是否使用 DPDK。</li>
<li>二层转发（<strong>Network Layer 2 Forwarding</strong>）：网络第 2 层转发，或者应用程序确实基于以太网 MAC 地址进行转发，就像一个简单的交换机。<code>l2fwd</code></li>
<li>三层转发(<strong>Network Layer 3 Forwarding</strong>)：网络第 3 层转发，或应用程序确实基于互联网协议、IPv4 或 IPv6 进行转发，就像一个简单的路由器。<code>l3fwd</code></li>
<li>数据包分发器(<strong>Packet Distributor</strong>)：数据包分发器演示如何将到达 Rx 端口的数据包分发到不同的内核进行处理和传输。</li>
<li>多进程应用程序(<strong>Multi-process Application</strong>)：多进程应用程序显示两个 DPDK 进程如何使用队列和内存池来共享信息。</li>
<li>RX&#x2F;TX回调应用程序(<strong>RX&#x2F;TX Callbacks Application</strong>) ：RX&#x2F;TX回调示例应用程序是一个数据包转发应用程序，演示用户在接收和传输的数据包上使用用户定义的回调。应用程序通过向 RX 和 TX 数据包处理函数添加回调来计算 RX（数据包到达）和 TX（数据包传输）之间的数据包延迟。</li>
<li>IPSec 安全网关(<strong>IPsec Security Gateway</strong>)：IPSec 安全网关应用程序是更接近真实示例的最小示例。这也是使用 DPDK Cryptodev 框架的应用程序的一个很好的示例。</li>
<li>精度时间协议 （<strong>Precision Time Protocol (PTP) Client</strong>)客户端：PTP 客户端是实际应用程序的另一个最小实现。在这种情况下，应用程序是一个 PTP 客户端，它与 PTP 主时钟通信，使用 IEEE1588 协议在网络接口卡 （NIC） 上同步时间。</li>
<li>服务质量调度程序 （<strong>QoS Scheduler</strong>） ：QoS 调度程序应用程序演示使用 DPDK 提供 QoS 调度。</li>
</ul>
<h2 id="DPDK线程启动方式"><a href="#DPDK线程启动方式" class="headerlink" title="DPDK线程启动方式"></a>DPDK线程启动方式</h2><h3 id="dpdk线程"><a href="#dpdk线程" class="headerlink" title="dpdk线程"></a>dpdk线程</h3><p>rte_eal_init执行时会通过pthread_create创建一个worker线程(eal_thread_loop)，并绑定到配置的lcore上，<br>可通过参数-c&#x2F;-l&#x2F;–lcores指定在那些核心创建worker线程。<br>每个worker线程拥有一对管道fd用于与master线程通信。<br>worker线程内部时一个while超循环结构，在循环开始处通过read管道阻塞线程。<br>master线程调用rte_eal_remote_launch或rte_eal_mp_remote_launch创建用户线程时，<br>实现上是通过write对应core的管道唤醒worker线程，再执行rte_eal_remote_launch传入的函数。</p>
<h3 id="几种用户线程初始化方式"><a href="#几种用户线程初始化方式" class="headerlink" title="几种用户线程初始化方式"></a>几种用户线程初始化方式</h3><p>在每个slave核上创建线程</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">RTE_LCORE_FOREACH_SLAVE</span>(lcore_id) </span><br><span class="line">	&#123; <span class="built_in">rte_eal_remote_launch</span>(user_loop, param, lcore_id); &#125;</span><br></pre></td></tr></table></figure>

<p>在除了master核的其他核上创建线程</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rte_eal_mp_remote_launch</span>(user_loop, param, SKIP_MASTER);</span><br></pre></td></tr></table></figure>

<p>上面两种dpdk线程初始化方式都会跳过core 0 的0核(默认的master核心)<br>那么问题来了<br>对于启用了超线程的cpu，一个物理核上会存在两个两个逻辑核(lcore)<br>那么data path的任务是否适合放在上面这个cpu的core0-1上呢？<br>拍脑袋想也应该时不合适的吧？<br>那么在线程初始化的适合，是否应该根据cpu分布，</p>
<ol>
<li>避免在master物理核心的两个lcore上跑data path任务</li>
<li>在每个物理核上仅选择一个lcore创建任务</li>
</ol>
<p>理由是，超线程的两个逻辑核心是共享流水线和L1&#x2F;L2 Cache的，超线程是否适合dpdk？</p>
<p>不知道为啥，在大部分代码里看到的是上面这两种任务初始化，而不是根据物理核心初始化</p>
<h3 id="基于DPDK的接收报文模式有两种物理实现框架："><a href="#基于DPDK的接收报文模式有两种物理实现框架：" class="headerlink" title="基于DPDK的接收报文模式有两种物理实现框架："></a>基于DPDK的接收报文模式有两种物理实现框架：</h3><ul>
<li>RTC（run to complement）是指所有处理都放在一个cpu上进行，从RX到Filter到TX等过程都由一个进程进行处理</li>
<li>Pipeline流水线模式是由多个进程进行共同处理，RX在一个进程，Filter在另一个进程，等等，通过环形队列的方式不同进程之间进行报文的传递</li>
</ul>
<p>PPK平台是在RTC模型框架下进行的开发工作，因此需要针对Qos功能进行新的开发（Pipeline有现有的基于DPDk的Qos处理方式）。</p>
<p>程序处理流程：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">parse_args</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span>&#123;</span><br><span class="line">    <span class="comment">//lcore_params[nb_lcore_params].port_id = (uint8_t)int_fld[FLD_PORT];</span></span><br><span class="line">    <span class="comment">//lcore_params[nb_lcore_params].queue_id = (uint8_t)int_fld[FLD_QUEUE];</span></span><br><span class="line">    <span class="comment">//lcore_params[nb_lcore_params].lcore_id = (uint8_t)int_fld[FLD_LCORE];</span></span><br><span class="line">    ret = <span class="built_in">parse_lcore_params</span>(optarg);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">initialize_args</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">rte_eal_init</span>();<span class="comment">//dpdk 环境抽象层的初始化</span></span><br><span class="line">    <span class="built_in">parse_args</span>(argc,argv);<span class="comment">//解析传入参数，-l -c -n --lcores之类的</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">initialize_args</span>();</span><br><span class="line">    <span class="built_in">initialize_nic</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="DPDK的多线程"><a href="#DPDK的多线程" class="headerlink" title="DPDK的多线程"></a>DPDK的多线程</h3><p>DPDK通过在多核设备上创建多线程，每个线程绑定到单独的核上。</p>
<p>在initialize_args里调用DPDK多线程的处理机制，针对每一个网卡port，运行一个新线程，进程分为master线程和slave线程</p>
<p><img src="https://pic.imgdb.cn/item/62329dcc5baa1a80ab3f100d.jpg"></p>
<p>DPDK线程基于pthread接口创建，属于抢占式线程模型，受内核调度支配。通过在多核设备上创建多个线程，每个线程绑定到单独的核上，减少线程调度的开销， 以提高性能。控制线程一般绑定到MASTER核上，接受用户配置，并传递配置参数给数据线程等；数据线程分布在不同核上处理数据包。</p>
<h3 id="对于网卡开启多队列的流程："><a href="#对于网卡开启多队列的流程：" class="headerlink" title="对于网卡开启多队列的流程："></a>对于网卡开启多队列的流程：</h3><ol>
<li>启动时进行端口的配置，这部分需要配置的内容包括设置收包的模式，同时指定收包时使用的哈希函数作用的地方，两者缺一不可；</li>
<li>启动多个队列，这个肯定没问题；</li>
<li>利用DPDK自带的统计工具无法显示相关的队列的数据，这个不知道为什么，需要后续进行源码的阅读后做定夺。<br> <strong>所以，测试时只需要从多个队列上进行取数据即可，发现不同队列上都有数据。</strong></li>
</ol>
<p>作者：VChao<br>链接：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/c6686b022019">https://www.jianshu.com/p/c6686b022019</a><br>来源：简书<br>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p>
<p> -c 0xff -n 8 – -p 0x3 –config “&quot;(0,0,0),(0,1,1),(0,2,2),(1,0,3),(1,1,4),(1,2,5)&quot;“</p>
<h3 id="网卡的操作"><a href="#网卡的操作" class="headerlink" title="网卡的操作"></a>网卡的操作</h3><p><img src="https://pic.imgdb.cn/item/623c87c527f86abb2aabf967.jpg" alt="接收报文流程"></p>
<p>智能网卡允许DMA操作和多队列。当配置完多队列的参数后，在启动时，调用initialize_nic();函数，将参数配置到网卡上，网卡开启多队列。DMA由一段连续缓存的环形队列和寄存器构成。（这里引申一下DMA技术，DMA打的实现还要依赖于intel的DDIO，即允许DMA引擎直接操作last level cache，这样cpu就可以尽可能少的参与io操作）寄存器包括base,head,tail,lengh。缓存通常采用一段物理上连续的内存。队列中存放描述符，不管是接收队列还是发送队列，主要有这么几个操作：</p>
<ol>
<li>填充缓冲区地址（mbuf）到描述符；</li>
<li>移动tail尾指针；</li>
<li>判断描述符中的判断位，确定是否收&#x2F;发完成。</li>
</ol>
<p>需要注意的是，环形队列的内容在内存中，但是控制部分寄存器是在网卡上的硬件。</p>
<p>关于NIC、cpu、内存（LLC last level cache）三者一起接收报文的流程：</p>
<ol>
<li>cpu将缓存地址放入环形队列中可用的描述符中；</li>
<li>DMA引擎读取描述符获取该缓存地址，将报文放入到该地址的内存（缓存）空间；</li>
<li>网卡回写接收侧描述符，改写描述符状态，确认接收完成的前提下；</li>
<li>CPU读取接收侧描述符，确认接收完成。</li>
<li>CPU去缓存地址读取报文做转发判断；</li>
<li>CPU对报文做填充；准备发送走；</li>
</ol>
<p>接下来进行发送操作：</p>
<ol>
<li>CPU读取发送侧描述符，确定是否发送（上一条报文）完成；</li>
<li>CPU将缓冲区地址写入发送侧描述符；</li>
<li>网卡DMA读取发送侧描述符缓冲地址；</li>
<li>去缓冲地址中取报文数据；</li>
<li>网卡写发送侧描述符，确认发送已经完成；</li>
</ol>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">uint16_t</span> <span class="title">eth_igb_recv_pkts</span><span class="params">(<span class="type">void</span> *rx_queue, <span class="keyword">struct</span> rte_mbuf **rx_pkts, <span class="type">uint16_t</span> nb_pkts)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="keyword">while</span> (nb_rx &lt; nb_pkts) </span><br><span class="line">	&#123;</span><br><span class="line">		<span class="comment">//从描述符队列中找到待被应用层最后一次接收的那个描述符位置</span></span><br><span class="line">		rxdp = &amp;rx_ring[rx_id];</span><br><span class="line">		staterr = rxdp-&gt;wb.upper.status_error;</span><br><span class="line">		<span class="comment">//检查状态是否为dd, 不是则说明驱动还没有把报文放到接收队列，直接退出</span></span><br><span class="line">		<span class="keyword">if</span> (! (staterr &amp; <span class="built_in">rte_cpu_to_le_32</span>(E1000_RXD_STAT_DD)))</span><br><span class="line">		&#123;</span><br><span class="line">			<span class="keyword">break</span>;</span><br><span class="line">		&#125;;</span><br><span class="line">		<span class="comment">//找到了描述符的位置，也就从软件队列中找到了mbuf</span></span><br><span class="line">		rxe = &amp;sw_ring[rx_id];</span><br><span class="line">		rx_id++;</span><br><span class="line">		rxm = rxe-&gt;mbuf;		</span><br><span class="line">		<span class="comment">//填充mbuf</span></span><br><span class="line">		pkt_len = (<span class="type">uint16_t</span>) (<span class="built_in">rte_le_to_cpu_16</span>(rxd.wb.upper.length) - rxq-&gt;crc_len);</span><br><span class="line">		rxm-&gt;data_off = RTE_PKTMBUF_HEADROOM;</span><br><span class="line">		rxm-&gt;nb_segs = <span class="number">1</span>;</span><br><span class="line">		rxm-&gt;pkt_len = pkt_len;</span><br><span class="line">		rxm-&gt;data_len = pkt_len;</span><br><span class="line">		rxm-&gt;port = rxq-&gt;port_id;</span><br><span class="line">		rxm-&gt;hash.rss = rxd.wb.lower.hi_dword.rss;</span><br><span class="line">		rxm-&gt;vlan_tci = <span class="built_in">rte_le_to_cpu_16</span>(rxd.wb.upper.vlan);</span><br><span class="line">		<span class="comment">//保存到应用层</span></span><br><span class="line">		rx_pkts[nb_rx++] = rxm;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//申请一个新的mbuf</span></span><br><span class="line">		nmb = <span class="built_in">rte_rxmbuf_alloc</span>(rxq-&gt;mb_pool);</span><br><span class="line">		<span class="comment">//因为原来的mbuf被应用层取走了。这里替换原来的软件队列mbuf，这样网卡收到报文后可以放到这个新的mbuf</span></span><br><span class="line">		rxe-&gt;mbuf = nmb;</span><br><span class="line">		dma_addr = <span class="built_in">rte_cpu_to_le_64</span>(<span class="built_in">RTE_MBUF_DATlA_DMA_ADDR_DEFAULT</span>(nmb));</span><br><span class="line">		<span class="comment">//将mbuf地址保存到描述符中，相当于高速dma控制器mbuf的地址。</span></span><br><span class="line">		rxdp-&gt;read.hdr_addr = dma_addr;			<span class="comment">//这里会将dd标记清0</span></span><br><span class="line">		rxdp-&gt;read.pkt_addr = dma_addr;		</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">uint16_t</span> <span class="title">rte_eth_rx_burst</span><span class="params">(<span class="type">uint8_t</span> port_id,  <span class="type">uint16_t</span> queue_id, <span class="keyword">struct</span> rte_mbuf **rx_pkts,  <span class="type">uint16_t</span> nb_pkts)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="comment">//如果是e1000网卡，则接收报文的接口为eth_igb_recv_pkts</span></span><br><span class="line">	<span class="keyword">return</span> (*dev-&gt;rx_pkt_burst)(dev-&gt;data-&gt;rx_queues[queue_id],	rx_pkts, nb_pkts);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>下图是一个大致的函数实现流程</p>
<p><img src="https://pic.imgdb.cn/item/62453e2a27f86abb2af06316.jpg"></p>
<h3 id="Classification功能"><a href="#Classification功能" class="headerlink" title="Classification功能"></a>Classification功能</h3><p>Classification功能是指网卡在收包时，将符合某种规则的包放入指定的队列。网卡一般支持一种或多种classification功能，以intel 700系列网卡为例，其支持MAC&#x2F;VLAN filter、Ethertype filter、Cloud filter、flow director等等。不同的网卡可能支持不同种类的filter，例如intel 82599系列网卡支持n-tuple、L2_tunnel、flow director等，下表列出了DPDK中不同驱动对filter的支持。即使intel 82599系列网卡和intel 700系列网卡都支持flow director，它们支持的方式也不一样。那DPDK是如何支持不同网卡的filter的呢？</p>
<p><img src="https://pic.imgdb.cn/item/624661ef27f86abb2ad77379.jpg"></p>
<h3 id="ring结构"><a href="#ring结构" class="headerlink" title="ring结构"></a>ring结构</h3>
        </div>

        
            <section class="post-copyright">
                
                    <p class="copyright-item">
                        <span>Author:</span>
                        <span>Ian Tang</span>
                    </p>
                
                
                    <p class="copyright-item">
                        <span>Permalink:</span>
                        <span><a href="https://tkhfree.github.io/2022/11/27/%E7%BD%91%E7%BB%9C%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E5%A5%97%E4%BB%B6DPDK-2022-11-27/">https://tkhfree.github.io/2022/11/27/%E7%BD%91%E7%BB%9C%E5%B9%B3%E5%8F%B0%E5%BC%80%E5%8F%91%E5%A5%97%E4%BB%B6DPDK-2022-11-27/</a></span>
                    </p>
                
                
                    <p class="copyright-item">
                        <span>License:</span>
                        <span>Copyright (c) 2019 <a target="_blank" rel="noopener" href="http://creativecommons.org/licenses/by-nc/4.0/">CC-BY-NC-4.0</a> LICENSE</span>
                    </p>
                
                
                     <p class="copyright-item">
                         <span>Slogan:</span>
                         <span>Do you believe in <strong>DESTINY</strong>?</span>
                     </p>
                

            </section>
        
        <section class="post-tags">
            <div>
                <span>Tag(s):</span>
                <span class="tag">
                    
                </span>
            </div>
            <div>
                <a href="javascript:window.history.back();">back</a>
                <span>· </span>
                <a href="/">home</a>
            </div>
        </section>
        <section class="post-nav">
            
            
            <a class="next" rel="next" href="/2022/11/27/Vscode%E7%BC%96%E8%BE%91C%E9%A1%B9%E7%9B%AE-2022-11-27/">Vscode编辑C项目</a>
            
        </section>


    </article>
</div>

            </div>
            <footer id="footer" class="footer">
    <div class="copyright">
        <span>© Ian Tang | Powered by <a href="https://hexo.io" target="_blank">Hexo</a> & <a href="https://github.com/Siricee/hexo-theme-Chic" target="_blank">Chic</a></span>
    </div>
</footer>

    </div>
</body>

</html>